/** uupaa-mutationevent.js
 *
 * uupaa-mutationevent.js is DOM Level 2 Mutation Events support javascript library
 *  - uupaa.js spin-off project
 *
 * @author Takao Obara <uupaa.js@gmail.com>
 * @license uupaa-mutationevent.js is licensed under the terms and conditions of the MIT licence.
 * @version 0.1
 * @date 2008-12-01
 * @see <a href="http://code.google.com/p/uupaa-js/">uupaa.js Home(Google Code)</a>
 * @see <a href="http://code.google.com/p/uupaa-js-spinoff/">uupaa.js SpinOff Project Home(Google Code)</a>
 * @see <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MutationEvent">MutationEvent</a>
 */
if (!uuClass.MutationEvent) {

/** Mutation
 *
 * @class static public
 */
uuClass.MutationEvent = function(fn, hook /* = uuConst.Mutation.All */,
                            enable /* = true */, ignore /* = "" */) {
  if (uuClass.MutationEvent.age) { return; } // already init
  var _mu = uuClass.MutationEvent, ary, i = 0, iz;
  _mu._fn = fn;
  _mu._bits = hook || 0x7;
  _mu._on = (enable === void 0) ? 1 : enable;
  if (ignore) {
    ary = ignore.replace(/\s+/g, "").split(","), iz = ary.length;
    for (; i < iz; ++i) { _mu._ig[ary[i]] = 1; }
    ++_mu._useig;
  }
  _mu._init();
};

// ---  enum event hook bits ---
uuConst.Mutation = {
  Tree: 0x1, // hook DOMSubtreeModified
  Node: 0x2, // hook DOMNodeInserted + DOMNodeRemoved
  Attr: 0x4, // hook DOMAttrModified
  All:  0x7  // hook all modified events
};

// --- local scope ------------------------------------------------------
(function() {
var _doc = document, _ua = uuClass.Detect, _mu = uuClass.MutationEvent,
    _hook = {},
    IE8NAMES = {
      appendChild: 0, insertBefore: 0, replaceChild: 0, removeChild: 0,
      insertAdjacentText: 0, insertAdjacentHTML: 0, insertAdjacentElement: 0,
      id: 3, className: 3, title: 3,
      innerHTML: 1, innerText: 1, outerHTML: 1, outerText: 1,
      setAttribute: 2, removeAttribute: 2
    },
    EVENTS = {
      T: "DOMSubtreeModified", I: "DOMNodeInserted", R: "DOMNodeRemoved",
      A: "DOMAttrModified", P: "onpropertychange"
    };

// uuClass.MutationEvent.age - ageing counter
_mu.age = 0;

// uuClass.MutationEvent._on - use event hook
_mu._on = 1;      // 1: event enable, 0: disable

// uuClass.MutationEvent._fn - callback function
_mu._fn = null;

// uuClass.MutationEvent._bits - event bits
_mu._bits = 0;    // see uuConst.Mutation

// uuClass.MutationEvent._uuid - uniqueID array for IE
_mu._uuid = [ /* IE::uniqueID */ ];

// uuClass.MutationEvent._ig - attr ignore list
_mu._ig = { /* title: 1, ... */ };

// uuClass.MutationEvent._useig - use attr ignore list
_mu._useig = 0;   // 1: use ignore list, 0: not use

_mu._stop = 0;    // 1: stop attr propagation

function enable() {
  if (!_mu._on) {
    _mu._on = 1;

    var bit = _mu._bits;
    if (bit & 1) { _doc.addEventListener(EVENTS.T, _mu._tree, false); }
    if (bit & 2) { _doc.addEventListener(EVENTS.I, _mu._node, false);
                   _doc.addEventListener(EVENTS.R, _mu._node, false); }
    if (bit & 4) { _doc.addEventListener(EVENTS.A, _mu._attr, false); }
  }
}

function disable() {
  if (_mu._on) {
    _mu._on = 0;
    ++_mu.age

    var bit = _mu._bits;
    if (bit & 1) { _doc.removeEventListener(EVENTS.T, _mu._tree, false); }
    if (bit & 2) { _doc.removeEventListener(EVENTS.I, _mu._node, false);
                   _doc.removeEventListener(EVENTS.R, _mu._node, false); }
    if (bit & 4) { _doc.removeEventListener(EVENTS.A, _mu._attr, false); }
  }
}

function enableIE() {
  if (!_mu._on) {
    _mu._on = 1;

    if (_mu._bits & 5) {
      var n = _mu._uuid, i = 0, iz = n.length, e, v;
      for (; i < iz; ++i) {
        if ( (v = n[i]) ) { 
          if ( (e = _doc.getElementById(v)) ) {
            e.attachEvent(EVENTS.P, _mu._attrIE);
          } else {
            delete n[i];
          }
        }
      }
    }
  }
}

function disableIE() {
  if (_mu._on) {
    _mu._on = 0;
    ++_mu.age

    if (_mu._bits & 5) {
      var n = _mu._uuid, i = 0, iz = n.length, e, v;
      for (; i < iz; ++i) {
        if ( (v = n[i]) ) { 
          if ( (e = _doc.getElementById(v)) ) {
            e.detachEvent(EVENTS.P, _mu._attrIE);
          } else {
            delete n[i];
          }
        }
      }
    }
  }
}

function enableIE8() {
  if (!_mu._on) {
    _mu._on = 1;

    var bit = _mu._bits,
        e = _doc.createElement("p"),
        p = e.constructor.prototype.constructor.prototype,
        s = e.style.constructor.prototype, i;

    for (i in e.style) { IE8NAMES[i] = 5; }

    for (i in IE8NAMES) {
      switch (IE8NAMES[i]) {
      case 0: // node method(appendChild)
        (bit&3)&&(function(i){
          _hook[i]=p[i];
          p[i]=function(a,b,c){var rv=_hook[i].call(this,a,b,c);_mu._nodeIE();return rv;};
        })(i);
        break;
      case 1: // prop setter(innerHTML)
        (bit&3)&&(function(i){
          _hook[i]=p.__lookupSetter__(i);
          p.__defineSetter__(i,function(a){_hook[i].call(this,a);_mu._nodeIE();});
        })(i);
        break;
      case 2: // attr method(setAttribute)
        (bit&5)&&(function(i){
          _hook[i]=p[i];
          p[i]=function(a,b,c){var rv=_hook[i].call(this,a,b,c);_mu._attrIE8(a);return rv;};
        })(i);
        break;
      case 3: // prop setter(className)
        (bit&5)&&(function(i){
          _hook[i]=p.__lookupSetter__(i);
          p.__defineSetter__(i,function(a){
            _hook[i].call(this,a);_mu._attrIE8(i);
          });
        })(i);
        break;
      case 5: // style setter(style.backgroundColor)
        (bit&5)&&(function(i){
          _hook[i]=s.__lookupSetter__(i);
          s.__defineSetter__(i,function(a){
            _hook[i].call(this,a);_mu._attrIE8(i);
          });
        })(i);
      }
    }
  }
}

function disableIE8() {
  if (_mu._on) {
    _mu._on = 0;
    ++_mu.age

    var bit = _mu._bits,
        e = _doc.createElement("p"),
        p = e.constructor.prototype.constructor.prototype,
        s = e.style.constructor.prototype, i;

    for (i in IE8NAMES) {
      switch (IE8NAMES[i]) {
      case 0:
      case 2:
        (bit&7)&&(function(i){
          _hook[i]&&(p[i]=_hook[i]);
        })(i);
        break;
      case 1:
      case 3:
        (bit&7)&&(function(i){
          _hook[i]&&p.__defineSetter__(i,_hook[i]);
        })(i);
        break;
      case 5:
        (bit&5)&&(function(i){
          _hook[i]&&s.__defineSetter__(i,_hook[i]);
        })(i);
      }
    }
  }
}

function init() {
  if (_mu._on) {
    _mu._on = 0;
    _mu.enable();
  }
}

function initIE() {
  _doc.createStyleSheet().cssText = "*{behavior:expression(uuClass.MutationEvent._hackIE(this))}";
}

function initIE8() {
  var n = _doc.getElementsByTagName("*"), i = 0, iz = n.length;
  for (; i < iz; ++i) {
    n[i].uniqueID;
  }
  if (_mu._on) {
    _mu._on = 0;
    _mu.enable();
  }
}

// @see http://d.hatena.ne.jp/uupaa/20081129/1227951320
_mu._hackIE = function(elm) {
  if (elm.style.behavior === "none") { return; } // guard: text selection + drag
  elm.style.behavior = "none"; // disable CSS::expression
  if (elm.nodeType !== 1 && elm.nodeType !== 3) { return; }

  _mu._uuid.push(elm.uniqueID);

  if (_mu._bits & 3) {
    (function(node) {
      var rm = node.removeChild;
      node.removeChild = function(oldChild) {
        var rv = rm(oldChild);
        _mu._on && _mu._nodeIE();
        return rv;
      }
    })(elm);
  }

  if (_mu._on) {
    (_mu._bits & 5) && elm.attachEvent(EVENTS.P, _mu._attrIE);
    _mu._nodeIE();
  }
};

// uuClass.MutationEvent._tree - hook DOMSubtreeModified
_mu._tree = function(evt) {
  if (_mu._on) {
    if (!_mu._stop) {
      ++_mu.age;
      _mu._fn(1);
    }
    _mu._stop = 0;
  }
};

// uuClass.MutationEvent._node - hook DOMNodeInserted and DOMNodeRemoved
_mu._node = function() {
  if (_mu._on) {
    ++_mu.age;
    _mu._fn(2);
  }
};

// uuClass.MutationEvent._attr - hook DOMAttrModified, with ignore list
_mu._attr = function(evt) {
  if (_mu._on) {
    if (!_mu._useig) {
      ++_mu.age;
      _mu._fn(4);
    } else {
      if (!(evt.attrName in _mu._ig)) {
        ++_mu.age;
        _mu._fn(4);
      } else {
        _mu._stop = 1; // stop bubble
      }
    }
  }
};

// uuClass.MutationEvent._nodeIE - hook DOMSubtreeModified, DOMNodeInserted and DOMNodeRemoved for IE
_mu._nodeIE = function() {
  if (_mu._on) {
    ++_mu.age;
    (_mu._bits & 2) && _mu._fn(2); // fire DOMNodeInserted, DOMNodeRemoved
    (_mu._bits & 1) && _mu._fn(1); // fire DOMSubtreeModified
  }
};

// uuClass.MutationEvent._attrIE - hook DOMAttrModified, with ignore list for IE
_mu._attrIE = function(evt)  {
  if (_mu._on) {
    if (!_mu._useig || (_mu._useig && !(evt.propertyName in _mu._ig))) {
      ++_mu.age;
      (_mu._bits & 4) && _mu._fn(4); // fire DOMAttrModified
      (_mu._bits & 1) && _mu._fn(1); // fire DOMSubtreeModified
    }
  }
};

// uuClass.MutationEvent._attrIE8 - hook DOMAttrModified, with ignore list for IE8
_mu._attrIE8 = function(name)  {
  if (_mu._on) {
    if (!_mu._useig || (_mu._useig && !(name in _mu._ig))) {
      ++_mu.age;
      (_mu._bits & 4) && _mu._fn(4); // fire DOMAttrModified
      (_mu._bits & 1) && _mu._fn(1); // fire DOMSubtreeModified
    }
  }
};

// uuClass.MutationEvent.enable - enable event hook
_mu.enable = _ua.ie8 ? enableIE8 : _ua.ie ? enableIE : enable;

// uuClass.MutationEvent.enable - disable event hook
_mu.disable = _ua.ie8 ? disableIE8 : _ua.ie ? disableIE : disable;

// uuClass.MutationEvent._init - initialize
_mu._init = _ua.ie8 ? initIE8 : _ua.ie ? initIE : init;

})(); // end (function())() scope

} // if (!uuClass.MutationEvent)

